package tech.waterism.algorithm.moea.PSO;

import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import tech.waterism.dto.*;
import tech.waterism.entity.FCurveHc;
import tech.waterism.enums.LogicEnum;
import tech.waterism.utils.ConvertZC;
import tech.waterism.utils.ForecastDispatchUtils;

import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;

@Slf4j
public class CommonFitness {
    public static double calFieldPowerN(List<ForecastDataDto> data, DispatchConstraint constrain) {
        return data.stream().filter(i -> i.getNPower() != null).mapToDouble(
                i -> CheckUtil.getConstraintDiff(constrain.getValue(), i.getNPower(), constrain.getLogic())).sum();
    }

    public static void calFieldPowerNV2(FPIndexDto fpIndexDto, List<ForecastDataDto> data,
                                        DispatchConstraint constrain) {
        //出力的上限约束 即：不高于 小于时，出力如果超出，则令出力=约束值即可，不需要惩罚
        if (constrain.getLogic().equals(LogicEnum.LTE) || constrain.getLogic().equals(LogicEnum.LT)) {
            double constrainValue = constrain.getValue();
            List<FCurveHc> curveHc = fpIndexDto.getCurveHc();
            for (ForecastDataDto datum : data) {
                if (datum.getNPower() != null) {
                    double nPower = datum.getNPower();
                    if (nPower > constrainValue) {
                        datum.setNPower(constrainValue);
                        Double c = ConvertZC.getCfromZ(datum.getRz(), curveHc);
                        if (c < 0) {
                            c = 0.0001;
                        }
                        Double qPower = constrainValue * c * 1000 / 3600;
                        datum.setQPower(qPower);
                    }
                }
            }
        }
    }

    public static double calFieldPowerQ(List<ForecastDataDto> data, DispatchConstraint constrain) {
        return data.stream().filter(i -> i.getQPower() != null).mapToDouble(
                i -> CheckUtil.getConstraintDiff(constrain.getValue(), i.getQPower(), constrain.getLogic())).sum();
    }

    public static double calFieldCtlCond(LogFieldDto logFieldDto, List<ForecastDataDto> data,
                                         DispatchConstraint constrain, FPIndexDto fpIndexDto) {
        double punish = 0;
        for (int i = 0; i < data.size(); i++) {
            ForecastDataDto datum = data.get(i);
            punish += getConditionCtlCond(logFieldDto, constrain.getCtlCond(), datum, fpIndexDto, i);
        }
        return punish;
    }

    @SneakyThrows
    public static double getConditionCtlCond(LogFieldDto logFieldDto, PrincipleRecord record, ForecastDataDto datum,
                                             FPIndexDto fpIndex, int timeNum) {
        FloodCharacterDto floodCharacter = fpIndex.getFloodCharacter();
        double punish = 0;
        for (PrincipleCondition condition : record.getConditions()) {
            double value = (double) CheckUtil.obj2Map(datum).get(condition.getCompareField().getField());
            double lowerBound =
                    condition.getLowerBound() == null ? 0 : CheckUtil.getValue(logFieldDto, condition.getLowerBound(),
                            floodCharacter, datum);
            double upperBound = condition.getUpperBound() == null ? Double.MAX_VALUE : CheckUtil.getValue(logFieldDto,
                    condition.getUpperBound(), floodCharacter, datum);
            if (condition.getBoundLogic() == null) {
                throw new Exception("请选择范围逻辑");
            }
            boolean result = false;
            switch (condition.getBoundLogic()) {
                case OPEN:
                    if (lowerBound < value && value < upperBound) {
                        result = true;
                    }
                    break;
                case CLOSED:
                    if (lowerBound <= value && value <= upperBound) {
                        result = true;
                    }
                    break;
                case FORC:
                    if (lowerBound < value && value <= upperBound) {
                        result = true;
                    }
                    break;
                case FCRO:
                    if (lowerBound <= value && value < upperBound) {
                        result = true;
                    }
                    break;
                default:
                    throw new Exception("Unexpected value: " + condition.getBoundLogic().getName());
            }
            if (result) {
                for (PrincipleControl control : record.getControls()) {
                    double controlValue;
                    switch (control.getValueType()) {
                        case FIXED -> {
                            controlValue = control.getValue();
                        }
                        case CHARACTER -> {
                            // 若选中出流：入流、入流峰值、前期入流峰值、维持出流（前一时段出流）、泄流能力。
                            controlValue =
                                    ForecastDispatchUtils.getCharacterValueByExtra(control.getCatalog(), datum, fpIndex,
                                            timeNum, timeNum);
                        }
                        default -> {
                            throw new Exception("约束值类型错误");
                        }
                    }
                    punish += CheckUtil.getConstraintDiff(controlValue, value, control.getLogic());
                }
            }
        }
        return punish;
    }

    public static boolean checkField(LogFieldDto logFieldDto, List<ForecastDataDto> checkData,
                                     DispatchConstraint constrain, FloodCharacterDto floodCharacter) throws Exception {
        String compareField = constrain.getCompareField().getField();
        switch (constrain.getValueType()) {
            case FIXED:
                for (ForecastDataDto checkDatum : checkData) {
                    Object value = CheckUtil.obj2Map(checkDatum).get(compareField);
                    if (value == null) {
                        throw new Exception(
                                logFieldDto.getStnm() + "(" + logFieldDto.getStcd() + ")" + "约束值" + compareField +
                                        "不存在");
                    }
                    boolean checkLogic =
                            CheckUtil.checkLogic(constrain.getValue(), (Double) value, constrain.getLogic());
                    if (!checkLogic) {
                        return false;
                    }
                }
                return true;
            case CHARACTER:
                for (ForecastDataDto checkDatum : checkData) {
                    Double constrainValue =
                            CheckUtil.getCharacterValue(logFieldDto, constrain.getCatalog(), floodCharacter,
                                    checkDatum);
                    Object value = CheckUtil.obj2Map(checkDatum).get(compareField);
                    if (value == null) {
                        throw new Exception(
                                logFieldDto.getStnm() + "(" + logFieldDto.getStcd() + ")" + "约束值" + compareField +
                                        "不存在");
                    }
                    boolean checkLogic = CheckUtil.checkLogic(constrainValue, (Double) value, constrain.getLogic());
                    if (!checkLogic) {
                        return false;
                    }
                }
                return true;
            default:
                return false;
        }
    }

    public static CheckValueDto getConstrainValue(LogFieldDto logFieldDto, ForecastDataDto checkDatum,
                                                  DispatchConstraint constrain, FloodCharacterDto floodCharacter)
            throws Exception {
        CheckValueDto result = new CheckValueDto();
        String compareField = constrain.getCompareField().getField();
        Double constrainValue;
        Object value = CheckUtil.obj2Map(checkDatum).get(compareField);
        switch (constrain.getValueType()) {
            case FIXED:
                constrainValue = constrain.getValue();
                break;
            case CHARACTER:
                constrainValue =
                        CheckUtil.getCharacterValue(logFieldDto, constrain.getCatalog(), floodCharacter, checkDatum);
                break;
            default:
                throw new Exception("约束值类型错误");
        }
        boolean checkLogic = CheckUtil.checkLogic(constrainValue, (Double) value, constrain.getLogic());
        // todo remove log
        log.debug(
                "约束值：" + constrainValue + "，实际值：" + value + "，逻辑：" + constrain.getLogic().getName() + "，结果：" +
                        checkLogic);
        if (!checkLogic) {
            result.setValue(constrainValue);
            result.setChanged(true);
        } else {
            result.setValue((Double) value);
            result.setChanged(false);
        }
        return result;
    }

    public static double calField(LogFieldDto logFieldDto, List<ForecastDataDto> checkData,
                                  DispatchConstraint constrain, FloodCharacterDto floodCharacter) throws Exception {
        String compareField = constrain.getCompareField().getField();
        double punish = 0;
        switch (constrain.getValueType()) {
            case FIXED:
                for (ForecastDataDto checkDatum : checkData) {
                    Object value = CheckUtil.obj2Map(checkDatum).get(compareField);
                    if (value == null) {
                        throw new Exception(
                                logFieldDto.getStnm() + "(" + logFieldDto.getStcd() + ")" + "约束值" + compareField +
                                        "不存在");
                    }
                    punish += CheckUtil.getConstraintDiff(constrain.getValue(), (Double) value, constrain.getLogic());
                }
                break;
            case CHARACTER:
                for (ForecastDataDto checkDatum : checkData) {
                    Double constrainValue =
                            CheckUtil.getCharacterValue(logFieldDto, constrain.getCatalog(), floodCharacter,
                                    checkDatum);
                    Object value = CheckUtil.obj2Map(checkDatum).get(compareField);
                    if (value == null) {
                        throw new Exception(
                                logFieldDto.getStnm() + "(" + logFieldDto.getStcd() + ")" + "约束值" + compareField +
                                        "不存在");
                    }
                    punish += CheckUtil.getConstraintDiff(constrainValue, (Double) value, constrain.getLogic());
                }
                break;
            default:
                throw new Exception("约束值类型错误");
        }
        return punish;
    }

    public static double calFieldZend(LogFieldDto logFieldDto, ForecastDataDto forecastDataDto,
                                      DispatchConstraint constrain, FloodCharacterDto floodCharacter) throws Exception {
        double punish;
        if (forecastDataDto.getRz() == null) {
            throw new Exception(logFieldDto.getStnm() + "(" + logFieldDto.getStcd() + ")" + "约束值" + "rz" + "不存在");
        }
        switch (constrain.getValueType()) {
            case FIXED:
                punish = CheckUtil.getConstraintDiff(constrain.getValue(), forecastDataDto.getRz(),
                        constrain.getLogic());
                break;
            case CHARACTER:
                Double constrainValue = CheckUtil.getCharacterValue(logFieldDto, constrain.getCatalog(), floodCharacter,
                        forecastDataDto);
                punish = CheckUtil.getConstraintDiff(constrainValue, forecastDataDto.getRz(), constrain.getLogic());
                break;
            default:
                throw new Exception("约束值类型错误");
        }
        return punish;
    }

    /**
     * 检查闸门使用数量是否超过闸门数量
     */
    public static boolean checkNGates(FPIndexDto fpIndex, Date fymdh) {
        List<ForecastDataDto> checkData =
                fpIndex.getData().stream().filter(datum -> datum.getDt().after(fymdh)).collect(Collectors.toList());
        Integer[] ngates = new Integer[fpIndex.getSpillways().size()];
        for (int i = 0; i < fpIndex.getSpillways().size(); i++) {
            ngates[i] = fpIndex.getSpillways().get(i).getNgates();
        }
        for (ForecastDataDto checkDatum : checkData) {
            for (int i = 0; i < checkDatum.getNUsedGates().length; i++) {
                if (sumGates(checkDatum.getNUsedGates()[i]) > ngates[i]) {
                    return true;
                }
            }
        }
        return false;
    }

    public static int sumGates(Integer[] nUsedGate) {
        return Arrays.stream(nUsedGate).reduce(Integer::sum).orElseGet(() -> 0);
    }
}
